package liquidfs.data.ops;

import java.io.*;
import java.util.*;

import liquidfs.*;
import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;
import liquidfs.data.fs.*;

public class QueryDirectoryContentsOperation extends AbstractFileOperation<List<LiquidObject>> {
   
	public QueryDirectoryContentsOperation(PeerDevice origin, String filepath) {
		super(origin, filepath);
	}

	@Override
	public List<LiquidObject> execute(Application app, InputStream in, OutputStream out) {   
	   
	   // Get a reference to the file system
		LiquidFileSystem fs = app.getFileSystem();
		
		// Contents
      List<LiquidObject> contents = new ArrayList<LiquidObject>();
		
		// Get the local contents of this file system
		LiquidObject object = fs.getPath(this.getFilePath());
		LiquidDirectory directory = object.asDirectory();
		List<LocalLiquidObject> localContents = directory.getLocalContents();
		
		// Iterate through each device we're connected two, except the ones that have already been queried
		for (Link link : app.getConfiguration().getLinks()) {
		
		   // Skip devices that have already been queried
		   if (this.reached(link.getDevice())) {
		      continue;
		   }
		   
   		// Pass this message along to other links in the network
   		Result<List<LiquidObject>> result = fs.relay(link, this, null, null);
   		List<LiquidObject> remoteContents;
   		
   		// Get the contents; if this device had nothing to add, skip it
   		try {
   		   remoteContents = result.getData();
   		}
   		catch (AlreadyRespondedException e) {
   		   // We already have those files
   		   continue;
   		}
   		catch (CommunicationException e) {
   		   // We wont list those files
   		   continue;
   		}
   		
   		// Merge the remote contents with the local contents
   		LocalLiquidDirectory.mergeContentLists(contents, remoteContents);
		}
		
		// Original remote contents size
		int numRemoteFiles = contents.size();
		
		// Merge the remote contents with the local contents, noting that this is a storage device of the local files in their caches
      for (LocalLiquidObject localObject : localContents) {
         
         // Check if this object is unique to this file system (defaults to true)
         boolean unique = true;
         
         // Check if this object is also a remote file
         for (int i = 0; i < numRemoteFiles; ++i) {
            
            // Get the remote object at that index
            LiquidObject remoteObject = contents.get(i);
            
            // Check if it matches the local object
            if (localObject.getPath().equals(remoteObject.getPath())) {
               
               // If so, get its storage devices
               List<PeerDevice> storageDevices = remoteObject.getStorageDevices();
               
               // Add this device, if necessary
               if (!storageDevices.contains(app.getConfiguration().getLocalDevice()))
                  storageDevices.add(app.getConfiguration().getLocalDevice());
               
               // Set the local object's storage devices with everything we've found so far
               localObject.getMetadataCache().setStorageDevices(storageDevices);
               
               // Replace the remote object in the list with the local one (with the correct storage devices)
               contents.set(i, localObject);
               
               // Mark this as not being unique, since we found another copy of it
               unique = false;
               
               // Stop searching
               break;
            }
            
         }
         
         // If this file was unique
         if (unique) {
            
            // Mark this as the only storage device
            List<PeerDevice> storageDevices = new ArrayList<PeerDevice>(1);
            storageDevices.add(app.getConfiguration().getLocalDevice());
            localObject.getMetadataCache().setStorageDevices(storageDevices);
            
            // Add it to the list
            contents.add(localObject);
         }
      }
		
		// Return the list we've compiled
		return contents;
		
	}

}
